/**
 * TemplateManager class for managing workflow templates
 */

import type { WorkflowTemplateData } from '../types';
import { WorkflowTemplate } from './WorkflowTemplate';

/**
 * Event emitted when a template is registered
 */
export interface TemplateRegisteredEvent {
  type: 'template:registered';
  template: WorkflowTemplate;
}

/**
 * Event emitted when a template is unregistered
 */
export interface TemplateUnregisteredEvent {
  type: 'template:unregistered';
  templateId: string;
}

/**
 * Union type for all template manager events
 */
export type TemplateManagerEvent = TemplateRegisteredEvent | TemplateUnregisteredEvent;

/**
 * Event listener type for template manager events
 */
export type TemplateManagerEventListener = (event: TemplateManagerEvent) => void;

/**
 * Options for template search
 */
export interface TemplateSearchOptions {
  category?: string;
  tags?: string[];
  author?: string;
  namePattern?: string;
  descriptionPattern?: string;
}

/**
 * Manages a collection of workflow templates
 */
export class TemplateManager {
  private readonly _templates = new Map<string, WorkflowTemplate>();
  private readonly _eventListeners = new Set<TemplateManagerEventListener>();

  /**
   * Registers a new template
   * @param template - Template to register
   * @throws Error if template with same ID already exists
   */
  register(template: WorkflowTemplate): void {
    if (this._templates.has(template.id)) {
      throw new Error(`Template with ID '${template.id}' already exists`);
    }

    this._templates.set(template.id, template);
    this.emit({ type: 'template:registered', template });
  }

  /**
   * Registers a template from JSON data
   * @param data - Template data to register
   * @throws Error if template with same ID already exists
   */
  registerFromData(data: WorkflowTemplateData): void {
    const template = new WorkflowTemplate(data);
    this.register(template);
  }

  /**
   * Unregisters a template by ID
   * @param templateId - ID of template to unregister
   * @returns True if template was found and removed
   */
  unregister(templateId: string): boolean {
    const existed = this._templates.delete(templateId);
    if (existed) {
      this.emit({ type: 'template:unregistered', templateId });
    }
    return existed;
  }

  /**
   * Gets a template by ID
   * @param templateId - ID of template to retrieve
   * @returns Template or undefined if not found
   */
  findById(templateId: string): WorkflowTemplate | undefined {
    return this._templates.get(templateId);
  }

  /**
   * Gets a template by ID, throwing if not found
   * @param templateId - ID of template to retrieve
   * @returns Template
   * @throws Error if template not found
   */
  getById(templateId: string): WorkflowTemplate {
    const template = this.findById(templateId);
    if (!template) {
      throw new Error(`Template with ID '${templateId}' not found`);
    }
    return template;
  }

  /**
   * Finds templates by category
   * @param category - Category to search for
   * @returns Array of templates in the category
   */
  findByCategory(category: string): WorkflowTemplate[] {
    return Array.from(this._templates.values())
      .filter(template => template.category === category);
  }

  /**
   * Finds templates by tag
   * @param tag - Tag to search for
   * @returns Array of templates with the tag
   */
  findByTag(tag: string): WorkflowTemplate[] {
    return Array.from(this._templates.values())
      .filter(template => template.tags.includes(tag));
  }

  /**
   * Finds templates by multiple tags (AND operation)
   * @param tags - Tags to search for
   * @returns Array of templates that have all specified tags
   */
  findByTags(tags: string[]): WorkflowTemplate[] {
    return Array.from(this._templates.values())
      .filter(template => tags.every(tag => template.tags.includes(tag)));
  }

  /**
   * Searches templates with various criteria
   * @param options - Search options
   * @returns Array of matching templates
   */
  search(options: TemplateSearchOptions): WorkflowTemplate[] {
    return Array.from(this._templates.values()).filter(template => {
      // Category filter
      if (options.category && template.category !== options.category) {
        return false;
      }

      // Tags filter (all tags must be present)
      if (options.tags && !options.tags.every(tag => template.tags.includes(tag))) {
        return false;
      }

      // Author filter
      if (options.author && template.metadata.author !== options.author) {
        return false;
      }

      // Name pattern filter
      if (options.namePattern) {
        const regex = new RegExp(options.namePattern, 'i');
        if (!regex.test(template.name)) {
          return false;
        }
      }

      // Description pattern filter
      if (options.descriptionPattern && template.description) {
        const regex = new RegExp(options.descriptionPattern, 'i');
        if (!regex.test(template.description)) {
          return false;
        }
      }

      return true;
    });
  }

  /**
   * Lists all registered templates
   * @returns Array of all templates
   */
  list(): WorkflowTemplate[] {
    return Array.from(this._templates.values());
  }

  /**
   * Gets all unique categories
   * @returns Array of unique categories
   */
  getCategories(): string[] {
    const categories = new Set<string>();
    for (const template of this._templates.values()) {
      if (template.category) {
        categories.add(template.category);
      }
    }
    return Array.from(categories).sort();
  }

  /**
   * Gets all unique tags
   * @returns Array of unique tags
   */
  getTags(): string[] {
    const tags = new Set<string>();
    for (const template of this._templates.values()) {
      for (const tag of template.tags) {
        tags.add(tag);
      }
    }
    return Array.from(tags).sort();
  }

  /**
   * Gets all unique authors
   * @returns Array of unique authors
   */
  getAuthors(): string[] {
    const authors = new Set<string>();
    for (const template of this._templates.values()) {
      if (template.metadata.author) {
        authors.add(template.metadata.author);
      }
    }
    return Array.from(authors).sort();
  }

  /**
   * Checks if a template exists
   * @param templateId - ID to check
   * @returns True if template exists
   */
  has(templateId: string): boolean {
    return this._templates.has(templateId);
  }

  /**
   * Gets the number of registered templates
   * @returns Number of templates
   */
  get size(): number {
    return this._templates.size;
  }

  /**
   * Clears all registered templates
   */
  clear(): void {
    const templateIds = Array.from(this._templates.keys());
    this._templates.clear();
    
    // Emit unregistered events for all templates
    for (const templateId of templateIds) {
      this.emit({ type: 'template:unregistered', templateId });
    }
  }

  /**
   * Adds an event listener
   * @param listener - Event listener function
   */
  addEventListener(listener: TemplateManagerEventListener): void {
    this._eventListeners.add(listener);
  }

  /**
   * Removes an event listener
   * @param listener - Event listener function to remove
   * @returns True if listener was found and removed
   */
  removeEventListener(listener: TemplateManagerEventListener): boolean {
    return this._eventListeners.delete(listener);
  }

  /**
   * Removes all event listeners
   */
  removeAllEventListeners(): void {
    this._eventListeners.clear();
  }

  /**
   * Emits an event to all listeners
   * @private
   * @param event - Event to emit
   */
  private emit(event: TemplateManagerEvent): void {
    for (const listener of this._eventListeners) {
      try {
        listener(event);
      } catch (error) {
        console.error('Error in template manager event listener:', error);
      }
    }
  }
}
